
`include "common_header.verilog"

//  *************************************************************************
//  File : rx_axi4s_ff.v
//  *************************************************************************
//  This program is controlled by a written license agreement.
//  Unauthorized reproduction or use is expressly prohibited.
//  Copyright (c) 2017 MorethanIP
//  Muenchner Strasse 199, 85757 Karlsfeld, Germany
//  info@morethanip.com
//  http://www.morethanip.com
//  *************************************************************************
//  Designed by : Geoffrey Chacon
//  info@morethanip.com
//  *************************************************************************
//  Description : Receive FIFO to AXI4 interface converter.
//                This is a parameterizable width interface to the TX FIFO
//                Does not support null or position bytes in the AXI bus.
//                All data will be left aligned and only the last word (TLAST)
//                contains null bytes. 
//  Version     : $Id: rx_axi4s_ff.v,v 1.11 2018/10/31 09:27:09 gc Exp $
//  *************************************************************************
//
// Note: Set RXFIFO_RDY_DELAY to the actual FIFO ff_rx_rdy -> ~ff_rx_dval latency.
//       When no FIFO is present, then set it to zero. However, this will cause 
//       that a tready deassertion can corrupt a frame.
//       A small DFF can be instantiated by setting RXFIFO_RDY_DELAY > 0, but without an RX
//       FIFO, this can only work if tready can be guaranteed to not deassert frequently


module rx_axi4s_ff #(
        parameter FLOP_INPUTS     = 0,                   // Add input registers. Not really needed for this interface
        parameter FLOP_OUTPUTS    = 1,                   // Add input registers
        parameter RXFIFO_RDY_DELAY = 2,                  // Number of extra words after ff_rx_rdy deasserts
        parameter DATA_WIDTH      = 256,                 // TDATA width in bits (multiples of 8)
        parameter DATA_MOD        = Clogb2(DATA_WIDTH/8),// Set to log2(DATA_WIDTH/8)
        parameter KEEP_WIDTH      = DATA_WIDTH/8,        // TKEEP width. 1 bit per byte
        parameter USER_WIDTH      = 30                   // TUSER width, application specific
) (
        input                    reset,       // Active High Reset
        input                    clk,         // Application clock
        input                    cfg_write64, // Configures the FIFO to use only the lower 64-bit of the data bus. Only for FIFOs of 128-bits. Drive to 0 if unused
        input                    sw_reset,    // FIFO sync reset (clk)

        /// AXI4 interface
        output                   tvalid,        // tVALID -> valid transaction
        output                   tlast,         // tVALID -> last transaction for a frame
        output  [KEEP_WIDTH-1:0] tkeep,         // tKEEP  -> valid byte qualifier, 1 bit per byte in tdata
        output  [DATA_WIDTH-1:0] tdata,         // tDATA  -> frame data, left aligned
        output  [USER_WIDTH-1:0] tuser,         // tUSER  -> sideband data. 
        output                   terr,          // tERR   -> custom signal. OR to the error bit in tuser on the top level
        input                    tready,        // tREADY -> interface backpressure

        /// FIFO interface
        input                    ff_rx_dval,       //  Write Enable from application
        input                    ff_rx_sop,        //  Start of Packet
        input                    ff_rx_eop,        //  End of Packet
        input   [DATA_MOD-1:0]   ff_rx_emod,       //  Last Word Modulo
        input   [USER_WIDTH-1:0] ff_rx_user,       //  User data, decoded at the higher layer
        input   [DATA_WIDTH-1:0] ff_rx_data,       //  Data Out
        output                   ff_rx_rdy         //  almost full, fifo ready
);

// ----------------------------------------------------------------------------------------------------
// Local functions
// ----------------------------------------------------------------------------------------------------
// Constant log2
function integer Clogb2(input integer idepth);
begin : f_clogb2
        integer depth, i;
        depth = idepth;
        if (depth <= 1)
        begin
                Clogb2 = 1;
        end
        else
        begin
                depth = depth - 1;

                for (i = 0; depth > 0; i = i + 1)
                begin
                        depth = depth >> 1;
                end

                Clogb2 = i;
        end
end
endfunction

// ----------------------------------------------------------------------------------------------------
// Local parameters
localparam DATA_MOD64 = (DATA_MOD >= 3) ? 3 : DATA_MOD; // Used for widths > 64-bit when writing in 64-bit mode
localparam KEEP_WIDTH64 = (KEEP_WIDTH < 8) ? KEEP_WIDTH : 8;

// ----------------------------------------------------------------------------------------------------
// Local wires and registers
wire                   cfg_write64_s;      //  synchronized
reg                    ff_rx_dval_r;       //  data valid
//reg                    ff_rx_sop_r;        //  Start of Packet
reg                    ff_rx_eop_r;        //  End of Packet
reg   [DATA_MOD-1:0]   ff_rx_emod_r;       //  Last Word Modulo
reg   [USER_WIDTH-1:0] ff_rx_user_r;       //  User data, decoded at the higher layer
reg   [DATA_WIDTH-1:0] ff_rx_data_r;       //  Data Out

reg                    sw_reset_l;         // Latched software reset. Used to block frame leftovers
wire                   rx_ok;          // Enables inputs from the FIFO

// DFF interface
wire                   dff_rden;
wire                   dff_empty;
wire                   dff_afull;

wire                   dff_eop;        //  End of Packet
wire  [DATA_MOD-1:0]   dff_emod;       //  Last Word Modulo
wire  [USER_WIDTH-1:0] dff_user;       //  User data, decoded at the higher layer
wire  [DATA_WIDTH-1:0] dff_data;       //  Data Out
reg   [KEEP_WIDTH-1:0] dff_keep;       // tKEEP  -> valid byte qualifier, 1 bit per byte in tdata

reg                     tlast_force;    // Forces a tlast to be sent to the application
wire  [KEEP_WIDTH-1:0]  tkeep_force;    // Forced tlast_keep
reg                     tin_frame;      // Whether there is a frame in progress to the application

// ----------------------------------------------------------------------------------------------------
// Latch software reset
always @(posedge clk or posedge reset)
begin : p_sw_reset
        if (reset == 1'b 1)
        begin
                sw_reset_l <= 1'b 0;
        end
        else
        begin
                if (sw_reset == 1'b 1)
                begin
                        sw_reset_l <= 1'b 1;
                end
                else if (sw_reset_l == 1'b 1 && ff_rx_dval == 1'b 1 && (ff_rx_sop == 1'b 1 || ff_rx_eop == 1'b 1))
                begin
                        sw_reset_l <= 1'b 0;
                end
        end
end

assign rx_ok = ~sw_reset_l | (ff_rx_dval & ff_rx_sop);

// ----------------------------------------------------------------------------------------------------
// Input registers
generate
if (FLOP_INPUTS == 1)
begin : g_input_registers
        always @(posedge clk or posedge reset)
        begin : p_in_regs
                if (reset == 1'b 1)
                begin
                        ff_rx_dval_r <= 1'b 0;
//                      ff_rx_sop_r  <= 1'b 0;
                        ff_rx_eop_r  <= 1'b 0;
                        ff_rx_emod_r <= {DATA_MOD{1'b 0}};
                        ff_rx_user_r <= {USER_WIDTH{1'b 0}};
                        ff_rx_data_r <= {DATA_WIDTH{1'b 0}};
                end
                else
                begin
                        if (sw_reset == 1'b 1 || rx_ok == 1'b 0)
                        begin
                                ff_rx_dval_r <= 1'b 0;
//                              ff_rx_sop_r  <= 1'b 0;
                                ff_rx_eop_r  <= 1'b 0;
                                ff_rx_emod_r <= {DATA_MOD{1'b 0}};
                                ff_rx_user_r <= {USER_WIDTH{1'b 0}};
                                ff_rx_data_r <= {DATA_WIDTH{1'b 0}};
                        end
                        else if ((dff_rden == 1'b 0 || ff_rx_dval_r == 1'b 0) || RXFIFO_RDY_DELAY != 0)
                        begin
                                // If the read fifo delay is different than zero
                                // then the DFF below is used to absorb the latency
                                // Otherwise, the pipeline is stalled when dff_afull asserts
                                ff_rx_dval_r <= ff_rx_dval;
                                if (ff_rx_dval == 1'b 1)
                                begin
//                                      ff_rx_sop_r  <= ff_rx_sop;
                                        ff_rx_eop_r  <= ff_rx_eop;
                                        ff_rx_emod_r <= ff_rx_emod;
                                        ff_rx_user_r <= ff_rx_user;
                                        ff_rx_data_r <= ff_rx_data;
                                end
                        end
                end
        end

        if (RXFIFO_RDY_DELAY == 0)
        begin : g_ff_rx_rdy
                // No FIFO present, can't deassert ff_rx_rdy
                assign ff_rx_rdy = 1'b 1;
        end
        else
        begin : g_ff_rx_rdy
                assign ff_rx_rdy = ~dff_afull;
        end
end

else
begin : g_noinput_registers
        always @(*)
        begin : p_in_comb
                ff_rx_dval_r = ff_rx_dval & rx_ok;
//              ff_rx_sop_r  = ff_rx_sop;
                ff_rx_eop_r  = ff_rx_eop;
                ff_rx_emod_r = ff_rx_emod;
                ff_rx_user_r = ff_rx_user;
                ff_rx_data_r = ff_rx_data;
        end

        if (RXFIFO_RDY_DELAY == 0)
        begin : g_ff_rx_rdy
                // No FIFO present, can't deassert ff_rx_rdy
                assign ff_rx_rdy = 1'b 1;
        end
        else
        begin : g_ff_rx_rdy
                assign ff_rx_rdy = ~dff_afull;
        end
end
endgenerate

mtip_dffvec #(1) U_DFFSYNC (
        .reset  (reset),
        .clk    (clk),
        .i      (cfg_write64),
        .o      (cfg_write64_s)
);

// ----------------------------------------------------------------------------------------------------
// Input FIFO. Needed to compensate for the RX FIFO read latency. 
// If there is no RX FIFO instantiated, it could be removed, but then tready
// would not be supported

generate
if (RXFIFO_RDY_DELAY == 0)
begin : g_nodff_fifo
        // With zero delay, there is no need for a DFF to absorb the data
        // It is assumed that the assertion of ff_tx_rdy stops the pipeline
        assign dff_eop  = ff_rx_eop_r;
        assign dff_emod = ff_rx_emod_r;
        assign dff_user = ff_rx_user_r;
        assign dff_data = ff_rx_data_r;

        assign dff_empty = ~ff_rx_dval_r;
        assign dff_afull = 1'b 0;
end

else 
begin : g_dff_fifo
        localparam DFF_WIDTH = 1+DATA_MOD+USER_WIDTH+DATA_WIDTH;
        localparam DFF_AFULL_THR = FLOP_INPUTS + RXFIFO_RDY_DELAY;
        localparam DFF_DEPTH = 2 + DFF_AFULL_THR;
        localparam DFF_ADDR_WIDTH = Clogb2(DFF_DEPTH);

        wire                   dff_wren;
        wire [DFF_WIDTH-1:0]   dff_din;
        wire [DFF_WIDTH-1:0]   dff_dout;

        assign dff_wren = ff_rx_dval_r;
        assign dff_din  = {ff_rx_eop_r,
                           ff_rx_emod_r,
                           ff_rx_user_r,
                           ff_rx_data_r};

        assign {dff_eop,
                dff_emod,
                dff_user,
                dff_data} = dff_dout;

        dff_fifo_noh_12347 #(
                .FF_WIDTH         (DFF_WIDTH),
                .ADDR_WIDTH       (DFF_ADDR_WIDTH),
                .DEPTH            (DFF_DEPTH),
                .AF_THRESHOLD     (DFF_AFULL_THR),
                .AE_THRESHOLD     (0),
        //      .ZERO_LATENCY     (1),  //  If 1 zero latency read; If 0 emulate memory 1 cycle read data delay
                .UNDERFLOW_PROTECT(0)  //  If 1 stop read pointer advance when FIFO is empty; If 0 read pointer is unprotected

        ) U_DFFFIFO1234 (

                .reset  (reset      ),
                .clk    (clk        ),
                .sw_reset(sw_reset  ),
                .wren   (dff_wren   ),
                .din    (dff_din    ),
                .rden   (dff_rden   ),
                .dout   (dff_dout   ),
                .ovr    (           ),
                .afull  (dff_afull  ),
                .full   (           ),
                .aempty (           ),
                .empty  (dff_empty  ),
                .fflevel(           )
        );
end
endgenerate


// Calculate tkeep (dff_keep) from ff_rx_mod (dff_emod)
always @(*)
begin : p_mod_to_keep
        integer i;

        dff_keep = {KEEP_WIDTH{1'b 0}}; // by default all bits are invalid

        if (dff_eop == 1'b 0 || 
            (cfg_write64_s == 1'b 0 && dff_emod == {DATA_MOD{1'b 0}}) ||
            (cfg_write64_s == 1'b 1 && dff_emod[DATA_MOD64-1:0] == {DATA_MOD64{1'b 0}}))
        begin
                // Non last word, all bytes valid; or
                // last word with mod = 0
                if (cfg_write64_s == 1'b 0)
                begin
                        // Assert keep for all bytes
                        dff_keep = {KEEP_WIDTH{1'b 1}};
                end
                else
                begin
                        // Only assert 8 bytes
                        dff_keep[KEEP_WIDTH64-1:0] = {KEEP_WIDTH64{1'b 1}};
                end
        end
        else
        begin
                // Last word with mod != 0
                if (cfg_write64_s == 1'b 0)
                begin
                        dff_keep[0] = 1'b 1; // Always at least one byte
                        // Set as many ones as dff_dout
                        for (i = 1; i < KEEP_WIDTH; i=i+1)
                        begin
                                dff_keep[i] = (i < dff_emod) ? 1'b 1 : 1'b 0;
                        end
                end

                else
                begin
                        dff_keep[0] = 1'b 1; // Always at least one byte
                        // Set as many ones as dff_dout
                        for (i = 1; i < 8; i=i+1)
                        begin
                                dff_keep[i] = (i < dff_emod[DATA_MOD64-1:0]) ? 1'b 1 : 1'b 0;
                        end
                end
        end
end

// Track whether a frame is being sent to the appliation. In case of a sw_reset, force a tlast
always @(posedge clk or posedge reset)
begin : p_frame_monitor
        if (reset == 1'b 1)
        begin
                tin_frame   <= 1'b 0;
                tlast_force <= 1'b 0;
        end
        else
        begin
                // When sw_reset asserts, if we are in the middle of a frame, force the
                // last word to be errored. Also if the read delay is zero (no fifo) and
                // ready deasserts
                if ((sw_reset == 1'b 1 && tin_frame == 1'b 1 && (tvalid == 1'b 0 || tlast == 1'b 0 || tready == 1'b 0)) ||
                    (RXFIFO_RDY_DELAY == 0 && tvalid == 1'b 1 && tready == 1'b 0 && tlast == 1'b 0))
                begin
                        // Force a tlast
                        tlast_force <= 1'b 1;

                        // Clear in frame
                        tin_frame <= 1'b 0;
                end

                else if (tlast_force == 1'b 1)
                begin
                        // Forced transaction sent
                        tlast_force <= ~tready;
                end

                // When passing data, keep track of the frame active state. This is to generate an SOP
                // after every tlast (i.e. the first tvalid after a tlast).
                else if (tvalid == 1'b 1 && tlast == 1'b 0)
                begin
                        tin_frame <= 1'b 1;
                end

                else if (tvalid == 1'b 1 && tlast == 1'b 1)
                begin
                        tin_frame <= 1'b 0;
                end
        end
end

// Value to force when forcing a frame end, basically one byte if tlast_force is set
assign tkeep_force = {{KEEP_WIDTH-1{1'b 0}}, tlast_force};


// ----------------------------------------------------------------------------------------------------
// Outputs. May need to be flopped if timing is not good enough
generate
if (FLOP_OUTPUTS == 1'b 1) 
begin : g_output_registers
        /// FIFO interface
        reg                    tvalid_r;
        reg                    tlast_r;
        reg   [KEEP_WIDTH-1:0] tkeep_r;
        reg   [DATA_WIDTH-1:0] tdata_r;
        reg   [USER_WIDTH-1:0] tuser_r;

        always @(posedge clk or posedge reset)
        begin : p_out_regs
                if (reset == 1'b 1)
                begin
                        tvalid_r <= 1'b 0;
                        tlast_r  <= 1'b 0;
                        tkeep_r  <= {KEEP_WIDTH{1'b 0}};
                        tdata_r  <= {DATA_WIDTH{1'b 0}};
                        tuser_r  <= {USER_WIDTH{1'b 0}};
                end
                else
                begin
                        if (sw_reset == 1'b 1)
                        begin
                                tvalid_r <= 1'b 0;
                                tlast_r  <= 1'b 0;
                                tkeep_r  <= {KEEP_WIDTH{1'b 0}};
                                tdata_r  <= {DATA_WIDTH{1'b 0}};
                                tuser_r  <= {USER_WIDTH{1'b 0}};
                        end
                        else if (tvalid_r == 1'b 0 || tready == 1'b 1)
                        begin
                                tvalid_r <= ~dff_empty;
                                tlast_r  <= ~dff_empty & dff_eop;
                                if (dff_rden == 1'b 1)
                                begin
                                        // Gate these signals with tvalid_r for fine-grained clock gating
                                        tkeep_r  <= dff_keep;
                                        tdata_r  <= dff_data;
                                        tuser_r  <= dff_user;
                                end
                        end
                end
        end

        // Drive the AXI interface from registers
        assign tvalid = tvalid_r | tlast_force;
        assign tlast  = tlast_r | tlast_force;
        assign tkeep  = tlast_force == 1'b 0 ? tkeep_r : tkeep_force;
        assign tdata  = tdata_r;
        assign tuser  = tuser_r;
        assign terr   = tlast_force;

        // FIFO read enable
        assign dff_rden = ~dff_empty & (~tvalid_r | tready) & ~tlast_force;
end

else
begin : g_nooutput_registers

        // Drive the AXI interface without registers
        assign tvalid = ~dff_empty | tlast_force;
        assign tlast  = ~dff_empty & (dff_eop | tlast_force);
        assign tkeep  = tlast_force == 1'b 0 ? dff_keep : tkeep_force;
        assign tdata  = dff_data;
        assign tuser  = dff_user;
        assign terr   = tlast_force;

        // FIFO read enable
        assign dff_rden = ~dff_empty & tready & ~tlast_force;
end
endgenerate

// ----------------------------------------------------------------------------------------------------
// Assertion checking

`ifndef SYNTHESIS
// synopsys translate_off
// synthesis translate_off
`ifdef MTIPASSERTIONS
always @(posedge clk or posedge reset)
begin : p_messages
        // Output a warning message if sw_reset causes a frame to be terminated with error
        if (reset == 1'b 0)
        begin
                if (tlast_force == 1'b 1 && tready == 1'b 1)
                begin
                        $display("%0t [rx_axi4s_ff.v] WARNING - sw_reset caused an early frame termination. (%m)", $time());
                end
        end
end

`ifdef MTIPASSERTIONS_SV
reg     ff_in_frame;

// Check for SOP and EOP violations at the input interface
always @(posedge clk or posedge reset)
begin : p_sop_eop_violations
        // Output an error message if a frame is truncated
        if (reset == 1'b 1 | sw_reset == 1'b 1)
        begin
                ff_in_frame <= 1'b 0;
        end
        else
        begin
                if (ff_rx_dval == 1'b 1 && ff_rx_sop == 1'b 1)
                begin
                        ff_in_frame <= ~ff_rx_eop;
                        assert (ff_in_frame == 1'b 0)
                           else $error($sformatf("%0t [rx_axi4s_ff.v] ERROR - FIFO protocol violation, unexpected SOP (%m).", $time()));
                end

                if (ff_rx_dval == 1'b 1 && ff_rx_eop == 1'b 1 && ff_rx_sop == 1'b 0)
                begin
                        ff_in_frame <= 1'b 0;
                        assert (ff_in_frame == 1'b 1)
                           else $error($sformatf("%0t [rx_axi4s_ff.v] ERROR - FIFO protocol violation, unexpected EOP (%m).", $time()));
                end
        end
end

// Check that dval deasserts after RXFIFO_RDY_DELAY maximum when tready deasserts
ASSUME__TREADY_DVAL_DELAY:
assume property (@(posedge clk) disable iff (reset || (RXFIFO_RDY_DELAY == 0))
                 ~ff_rx_rdy |-> ##RXFIFO_RDY_DELAY ~ff_rx_dval)
                 else $error($sformatf("%0t [rx_axi4s_ff.v] ERROR - ff_rx_dval is high after %0d cycles from ff_rx_rdy deassertion", $time, RXFIFO_RDY_DELAY));

ASSUME__TREADY_DVAL_NODELAY:
assume property (@(posedge clk) disable iff (reset || (RXFIFO_RDY_DELAY != 0))
                 ff_rx_dval && ~ff_rx_rdy |=> $stable({ff_rx_sop, ff_rx_eop, ff_rx_emod, ff_rx_user, ff_rx_data}))
                 else $error($sformatf("%0t [rx_axi4s_ff.v] ERROR - Data input not stable after ff_rx_rdy deassertion.", $time));

// Check that output data is stable when tready deasserts
ASSERT__STABLE_DATA_ON_TREADY_DEASSERTION:
assert property (@(posedge clk) disable iff (reset)
                 tvalid & ~tready |=> $stable({tvalid, tlast, tkeep, tdata, tuser}))
                 else $error($sformatf("%0t [rx_axi4s_ff.v] ERROR - Data output not stable after tready deassertion.", $time));

// Check that TKEEP is all 1's when TLAST is 0
ASSERT__TKEEP_CHK_1A:
assert property (@(posedge clk) disable iff (reset | cfg_write64_s)
                 tvalid & ~tlast |-> (&tkeep == 1'b 1))
                 else $error($sformatf("%0t [rx_axi4s_ff.v] ERROR - Invalid tKEEP value for when tLAST asserts.", $time));

ASSERT__TKEEP_CHK_1B:
assert property (@(posedge clk) disable iff (reset | ~cfg_write64_s)
                 tvalid & ~tlast |-> (&tkeep[KEEP_WIDTH64-1:0] == 1'b 1))
                 else $error($sformatf("%0t [rx_axi4s_ff.v] ERROR - Invalid tKEEP value for when tLAST asserts (64-bit write mode).", $time));

// Check that tkeep is valid for the interface (contiguous 1's) when not tlast
ASSERT__TKEEP_CHK_2A:
assert property (@(posedge clk) disable iff (reset | cfg_write64_s)
                 tvalid & tlast |-> ($countones({1'b 0,tkeep}+1) == 1))
                 else $error($sformatf("%0t [rx_axi4s_ff.v] ERROR - Invalid tKEEP value for when tLAST asserts.", $time));

ASSERT__TKEEP_CHK_2B:
assert property (@(posedge clk) disable iff (reset | ~cfg_write64_s)
                 tvalid & tlast |-> ($countones({1'b 0,tkeep}+1) == 1))
                 else $error($sformatf("%0t [rx_axi4s_ff.v] ERROR - Invalid tKEEP value for when tLAST asserts (64-bit write mode).", $time));

// Check that tkeep is never all zeroes when tvalid asserts
ASSERT__TKEEP_CHK_3A:
assert property (@(posedge clk) disable iff (reset | cfg_write64_s)
                 tvalid |-> (|tkeep == 1'b 1))
                 else $error($sformatf("%0t [rx_axi4s_ff.v] ERROR - Invalid tKEEP value (all-zeroes).", $time));

ASSERT__TKEEP_CHK_3B:
assert property (@(posedge clk) disable iff (reset | cfg_write64_s)
                 tvalid |-> (|tkeep[KEEP_WIDTH64-1:0] == 1'b 1))
                 else $error($sformatf("%0t [rx_axi4s_ff.v] ERROR - Invalid tKEEP value (all-zeroes) (64-bit write mode).", $time));

`endif

`endif
// synthesis translate_on
// synopsys translate_on
`endif


endmodule
